package com.firmboy.rpc;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * rpc框架demo
 *
 * @author firmboy
 * @create 2018-06-03 上午8:19
 **/
public class RpcFramework {

    public static void export(final Object service,int port) throws Exception {
        if(service==null){
            throw new IllegalStateException("service instance == null");
        }
        if(port<0||port>66535){
            throw new IllegalStateException("Invalid port "+port);
        }
        System.out.println("Export service "+service.getClass().getName()+" on port "+port);
        ServerSocket serverSocket = new ServerSocket(port);
        for(;;){
            try{
                final Socket socket = serverSocket.accept();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try{
                            try{
                                ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
                                try{
                                    String methodName = input.readUTF();
                                    Class<?>[] parameterTypes = (Class<?>[])input.readObject();
                                    Object[] arguments = (Object[]) input.readObject();
                                    ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                                    try{
                                        Method method = service.getClass().getMethod(methodName, parameterTypes);
                                        Object result = method.invoke(service, arguments);
                                        output.writeObject(result);
                                    }catch(Exception e){
                                        output.writeObject(e);
                                    }finally {
                                        output.close();
                                    }

                                }finally {
                                    input.close();
                                }

                            }finally {
                                socket.close();
                            }
                        }catch(Exception e){
                            e.printStackTrace();
                        }
                    }
                }).start();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }

    public static <T> T refer(final Class<T> interfaceClass ,final String host,final int port){
        if (interfaceClass ==null){
            throw new IllegalStateException(" InterfaceClass == null");
        }
        if(!interfaceClass.isInterface()){
            throw new IllegalStateException("the "+interfaceClass.getClass()+" must be interface class");
        }
        if(host ==null || host.length()==0){
            throw new IllegalStateException("Host == null");
        }
        if(port<=0||port>65535){
            throw new IllegalStateException("Invalid port "+port);
        }
        System.out.println("Get romote service "+interfaceClass.getClass().getName()+"from service "+host+":"+port);
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Socket socket = new Socket(host, port);
                try{
                    ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
                    try{
                        output.writeUTF(method.getName());
                        output.writeObject(method.getParameterTypes());
                        output.writeObject(args);
                        ObjectInputStream  input = new ObjectInputStream(socket.getInputStream());
                        try{
                            Object result = input.readObject();
                            if(result instanceof Throwable){
                                throw (Throwable) result;
                            }
                            return result;
                        }finally {
                            input.close();
                        }
                    }finally {
                        output.close();
                    }
                }finally {
                    socket.close();
                }
            }
        });
    }



}
